﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Text.RegularExpressions;
using ActivateYourGlutes.Infrastructure.MembershipServices;
using AutoMapper;
using Account=ActivateYourGlutes.Model.Internal.Account;
using Activity=ActivateYourGlutes.Model.Internal.Activity;
using UnitOfLength=ActivateYourGlutes.Model.Internal.UnitOfLength;
using UnitOfWeight=ActivateYourGlutes.Model.Internal.UnitOfWeight;

namespace ActivateYourGlutes.Model.Providers
{
    public class AccountProvider : ProviderBase, IAccountProvider
    {
        public AccountProvider(IEntityModelFactory factory) : base(factory)
        {
            
        }

        /// <summary>
        /// TODO:
        /// This is nasty but in one instance during controller initialisation I need access to the native
        /// account information in LocaleFactory.cs. Can be solved by pushing the LocaleFactory down here.
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public Account InternalCurrentAccount(IPrincipal user)
        {
            var username = user.Identity.Name;
            var account = Db.Accounts.Include("UnitOfLength").
                Include("UnitOfWeight").
                Include("PrimaryMeasurementType").
                Include("SecondaryMeasurementType").
                Where(ac => ac.Username == username).FirstOrDefault();

            return account;
        }

        /// <summary>
        /// Updates the details of an account
        /// </summary>
        public void UpdateAccount(Contracts.Account forAccount, DateTime dateOfBirth, Contracts.UnitOfLength unitOfLength, Contracts.UnitOfWeight unitOfWeight, IEnumerable<Contracts.MeasurementType> keyIndicators)
        {
            int keyIndicatorCount = keyIndicators.Count();
            if (keyIndicatorCount > 2)
                throw new ValidationException("_FORM", "A maximum of two key indicators may be selected.");

            var containerUnitOfLength =
                Db.UnitsOfLength.Where(ul => ul.UnitOfLengthId == unitOfLength.UnitOfLengthId).First();
            var containerUnitOfWeight =
                Db.UnitsOfWeight.Where(uw => uw.UnitOfWeightId == unitOfWeight.UnitOfWeightId).First();
            var account = Db.Accounts.Where(a => a.AccountId == forAccount.AccountId).First();
            account.DateOfBirth = dateOfBirth;
            account.UnitOfLength = containerUnitOfLength;
            account.UnitOfWeight = containerUnitOfWeight;
            account.PrimaryMeasurementType = null;
            account.SecondaryMeasurementType = null;

            if (keyIndicatorCount > 0)
            {
                account.PrimaryMeasurementTypeReference.EntityKey = new EntityKey("ActivateYourGlutesEntities.MeasurementTypeSet", "MeasurementTypeId", keyIndicators.First().MeasurementTypeId);
                if (keyIndicatorCount == 2)
                    account.SecondaryMeasurementTypeReference.EntityKey = new EntityKey("ActivateYourGlutesEntities.MeasurementTypeSet", "MeasurementTypeId", keyIndicators.Skip(1).First().MeasurementTypeId);
            }

            var errors = account.Validate();
            if (errors != null && errors.Count > 0)
                throw new ValidationException(errors);

            Db.SaveChanges();
        }

        /// <summary>
        /// Creates an account
        /// </summary>
        public void Create(string name, DateTime time, Contracts.UnitOfLength length, Contracts.UnitOfWeight weight, string dateFormat)
        {
            var account = new Account
                              {
                                  Username = name,
                                  DateOfBirth = new DateTime(1976, 1, 22),
                                  LocalDateFormat = dateFormat,
                                  UnitOfLengthReference =
                                      {
                                          EntityKey =
                                              new EntityKey("ActivateYourGlutesEntities.UnitsOfLength", "UnitOfLengthId",
                                                            length.UnitOfLengthId)
                                      },
                                  UnitOfWeightReference =
                                      {
                                          EntityKey =
                                              new EntityKey("ActivateYourGlutesEntities.UnitsOfWeight", "UnitOfWeightId",
                                                            weight.UnitOfWeightId)
                                      }
                              };

            var errors = account.Validate();
            if (errors != null && errors.Count > 0)
                throw new ValidationException(errors);

            Db.AddToAccounts(account);
            Db.SaveChanges();
        }

        /// <summary>
        /// Determines if any data has been logged by this user beyond account creation
        /// </summary>
        public bool IsDataLoggedForUser(IPrincipal principal)
        {
            var account = CurrentAccount(principal);
            var firstBody = Db.Bodies.Where(b => b.Account.AccountId == account.AccountId).FirstOrDefault();
            return firstBody != null;
        }

        /// <summary>
        /// Returns the recent activity the user has made against their account
        /// </summary>
        public IEnumerable<Contracts.Activity> RecentActivity(IPrincipal user)
        {
            var account = CurrentAccount(user);
            return Mapper.Map<List<Activity>, List<Contracts.Activity>>(Db.ActivitySet.Include("Body").Where(a => a.Account.AccountId == account.AccountId).OrderByDescending(a => a.ActivityDate).Take(5).ToList()); 
        }

        /// <summary>
        /// Validates the details supplied for logon
        /// </summary>
        public void ValidateLogOn(string userName, string password, IMembershipService membershipService)
        {
            var errors = new NameValueCollection();
            bool usernameIsEmpty = String.IsNullOrEmpty(userName);
            bool passwordIsEmpty = String.IsNullOrEmpty(password);
            if (usernameIsEmpty || passwordIsEmpty)
            {
                if (usernameIsEmpty)
                {
                    errors.Add("username", "You must specify a username.");
                }
                if (passwordIsEmpty)
                {
                    errors.Add("password", "You must specify a password.");
                }
            }
            else if (!membershipService.ValidateUser(userName, password))
            {
                errors.Add("_FORM", "The username or password provided is incorrect.");
            }

            if (errors.Count > 0)
                throw new ValidationException(errors);
        }

        /// <summary>
        /// Validates the details supplied for registration
        /// </summary>
        public void ValidateRegistration(string userName, string email, string password, string confirmPassword, bool acceptTerms, DateTime? dob, IMembershipService membershipService)
        {
            var errors = new NameValueCollection();
            if (dob == null)
            {
                errors.Add("dateOfBirth", "Date of birth is required and must use the format mm-dd-yyyy");
            }
            else
            {
                if (dob.Value.AddYears(18) > new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day))
                    errors.Add("dateOfBirth", "You must be at least 18 years old to use this site.");
            }
            if (String.IsNullOrEmpty(userName))
            {
                errors.Add("username", "You must specify a username.");
            }
            if (String.IsNullOrEmpty(email))
            {
                errors.Add("email", "You must specify an email address.");
            }
            else
            {
                Match match = Regex.Match(email, @"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b", RegexOptions.IgnoreCase);
                if (!match.Success)
                    errors.Add("email", "You must specify a valid email address.");
            }
            if (password == null || password.Length < membershipService.MinPasswordLength)
            {
                errors.Add("password",
                    String.Format(CultureInfo.CurrentCulture,
                         "You must specify a password of {0} or more characters.",
                         membershipService.MinPasswordLength));
            }
            if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
            {
                errors.Add("_FORM", "The new password and confirmation password do not match.");
            }
            if (!acceptTerms)
            {
                errors.Add("acceptTerms", "You must agree to the terms of service and privacy policy.");
            }

            if (errors.Count > 0)
                throw new ValidationException(errors);
        }

        /// <summary>
        /// Validates the details supplied for changing a password
        /// </summary>
        public void ValidateChangePassword(string currentPassword, string newPassword, string confirmPassword, IMembershipService membershipService)
        {
            var errors = new NameValueCollection();

            if (String.IsNullOrEmpty(currentPassword))
            {
                errors.Add("currentPassword", "You must specify a current password.");
            }
            if (newPassword == null || newPassword.Length < membershipService.MinPasswordLength)
            {
                errors.Add("newPassword",
                    String.Format(CultureInfo.CurrentCulture,
                         "You must specify a new password of {0} or more characters.",
                         membershipService.MinPasswordLength));
            }

            if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
            {
                errors.Add("_FORM", "The new password and confirmation password do not match.");
            }

            if (errors.Count > 0)
                throw new ValidationException(errors);
        }
    }
}